#ifndef _REGISTER_H_
#define _REGISTER_H_

#include "types.h"
#include "utils.h"
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <map>
#include <string>
#include <string_view>


class GPR {
private:
    word_t* gpr;
    std::map<std::string, word_t*> name2data;
public:
    static inline const std::string name_table[32] = {
        "$0", "ra",  "sp",  "gp",  "tp", "t0", "t1", "t2", 
        "s0",   "s1 ", "a0",  "a1",  "a2", "a3", "a4", "a5", 
        "a6",   "a7 ", "s2",  "s3",  "s4", "s5", "s6", "s7", 
        "s8",   "s9 ", "s10", "s11", "t3", "t4", "t5", "t6"
    };
    word_t& operator[] (const size_t index) {
        if (index >= 32) {
            throw std::runtime_error("GPR index out of bound.");
        }
        return gpr[index];
    }
    word_t& operator[] (const std::string name) {
        auto it = name2data.find(name);
        if (it == name2data.end()) {
            throw std::runtime_error("GPR name not found.");
        }
        return *it->second;
    }
    word_t* get_ptr() {
        return this->gpr;
    }
    void set_ptr(word_t* ptr) {
        this->gpr = ptr;
    }
    void dump() {
        printf("General Purpose Register: \n");
        for (int i = 0; i < 32; ++i) {
            printf("\t%-3s: " FMT_WORD "\t", name_table[i].data(), gpr[i]);
            if ((i+1)%2 == 0) printf("\n");
        }
    }
    void reset() {
        for (int i = 0; i < 32; ++i) {
            gpr[i] = 0;
            name2data[name_table[i]] = &gpr[i];
        }
    }
};

enum csr_addr {
    MSTATUS = 0x300,
    MIE = 0x304,
    MTVEC = 0x305,
    MEPC = 0x341,
    MCAUSE = 0x342,
    MIP = 0x344,
};


class CSR {
private:
    word_t* csr;
    std::map<std::string, word_t*>  name2data;
public:
    word_t& operator[] (const size_t index) {
        if (index >= 4096) {
            throw std::runtime_error("CSR index out of bound.");
        }
        return csr[index];
    }
    word_t& operator[] (const std::string name) {
        auto it = name2data.find(name);
        if (it == name2data.end()) {
            throw std::runtime_error("CSR name not found.");
        }
        return *it->second;
    }
    word_t* get_ptr() {
        return this->csr;
    }
    void set_ptr(word_t* ptr) {
        this->csr = ptr;
    }
    void dump() {
        printf("Control and Status Register: \n");
        printf("\t%-3s: " FMT_WORD "\t\n", "mstatus", csr[MSTATUS]);
        printf("\t%-3s: " FMT_WORD "\t\n", "mie", csr[MIE]);
        printf("\t%-3s: " FMT_WORD "\t\n", "mtvec", csr[MTVEC]);
        printf("\t%-3s: " FMT_WORD "\t\n", "mepc", csr[MEPC]);
        printf("\t%-3s: " FMT_WORD "\t\n", "mcause", csr[MCAUSE]);
        printf("\t%-3s: " FMT_WORD "\t\n", "mip", csr[MIP]);
    }
    void reset() {
        csr[MSTATUS] = 0x1800;
        name2data["mstatus"] = &csr[MSTATUS];

        csr[MIE] = 0;
        name2data["mie"] = &csr[MIE];

        csr[MTVEC] = 0;
        name2data["mtvec"] = &csr[MTVEC];

        csr[MEPC] = 0;
        name2data["mepc"] = &csr[MEPC];

        csr[MCAUSE] = 0;
        name2data["mcause"] = &csr[MCAUSE];

        csr[MIP] = 0;
        name2data["mip"] = &csr[MIP];
    }
};


struct mstatus_t {
    uint32_t WPRI_0 : 1;
    uint32_t SIE    : 1;
    uint32_t WPRI_1 : 1;
    uint32_t MIE    : 1;
    uint32_t WPRI_2 : 1;
    uint32_t SPIE   : 1;
    uint32_t UBE    : 1;
    uint32_t MPIE   : 1;
    uint32_t SPP    : 1;
    uint32_t VS     : 2;
    uint32_t MPP    : 2;
    uint32_t FS	    : 2;
    uint32_t XS     : 2;
    uint32_t MPRV   : 1;
    uint32_t SUM    : 1;
    uint32_t MXR    : 1;
    uint32_t TVM    : 1;
    uint32_t TW     : 1;
    uint32_t TSR    : 1;
#ifdef CONFIG_ISA64
    uint16_t WPRI_3 : 9;
    uint32_t UXL    : 2;
    uint32_t SXL    : 2;
    uint32_t SBE    : 1;
    uint32_t MBE    : 1;
    uint64_t WPRI_4 : 25;
    uint32_t SD     : 1;
#else
    uint32_t WPRI_3 : 8;
    uint32_t SD     : 1;
#endif
};

static_assert(sizeof(mstatus_t) == 8, "mstatus_t");

struct mip_t {
    uint64_t blank_0 : 1;
    uint64_t SSIP    : 1;
    uint64_t blank_1 : 1;
    uint64_t MSIP    : 1;
    uint64_t blank_2 : 1;
    uint64_t STIP    : 1;
    uint64_t blank_3 : 1;
    uint64_t MTIP    : 1;
    uint64_t blank_4 : 1;
    uint64_t SEIP    : 1;
    uint64_t blank_5 : 1;
    uint64_t MEIP    : 1;
    uint64_t blank_6 : XLEN - 12;
};

static_assert(sizeof(mip_t) == 8, "mip_t");

struct mie_t {
    uint64_t blank_0 : 1;
    uint64_t SSIE    : 1;
    uint64_t blank_1 : 1;
    uint64_t MSIE    : 1;
    uint64_t blank_2 : 1;
    uint64_t STIE    : 1;
    uint64_t blank_3 : 1;
    uint64_t MTIE    : 1;
    uint64_t blank_4 : 1;
    uint64_t SEIE    : 1;
    uint64_t blank_5 : 1;
    uint64_t MEIE    : 1;
    uint64_t blank_6 : XLEN - 12;
};

static_assert(sizeof(mie_t) == 8, "mie_t");

#endif
